package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"syscall"
	"unsafe"

	"fyne.io/fyne"
	"fyne.io/fyne/app"
	"fyne.io/fyne/widget"
	"github.com/hajimehoshi/oto"
)

/*
#cgo LDFLAGS: -Wl,-rpath=/usr/local/lib/xftts -L/usr/local/lib/xftts -lxtts
#include <stdlib.h>
extern int tts_init();
extern void tts_close();
extern int tts_puts(const char *src_text);
extern void* tts_get_pcm(int *audio_len,int *err); //PCM,单通道，采样率16000,每秒字节数32000
*/
import "C"

var skipN int
var curPos int = 0
var playPosLog int

var fullName string

func main() {
	os.Setenv("FYNE_FONT", "/usr/share/fonts/truetype/arphic/ukai.ttc")
	textCh := make(chan string)
	app := app.New()

	term := widget.NewLabelWithStyle("", fyne.TextAlignLeading, fyne.TextStyle{Bold: true})

	box1 := widget.NewHBox(term)
	term.Resize(fyne.NewSize(480, 320))
	box1.Resize(fyne.NewSize(480, 320))

	go func() {
		for s := range textCh {
			term.SetText(s)
		}
	}()

	w := app.NewWindow("xspeaker")
	w.SetContent(widget.NewVBox(box1))

	w.SetOnClosed(func() {
		app.Quit()
	})

	go speakFile(textCh)

	w.ShowAndRun()
}

func errPanic(err error) {
	if err != nil {
		panic(err)
	}
}

func speakFile(textCh chan string) {
	v := flag.Bool("log", false, "查看上次阅读记录。")
	skip := flag.Int("skip", 0, "跳过段数")
	flag.Parse()

	if *v {
		viewLog()
		return
	}

	skipN = *skip
	playPosLog = skipN
	var wg sync.WaitGroup
	var audioCh chan PcmMsg = make(chan PcmMsg, 1)
	wg.Add(1)
	go playBack(audioCh, textCh, &wg)

	fp, err := os.Open(flag.Arg(0))
	if err != nil {
		panic(err)
	}
	defer fp.Close()

	fullName = getFullName(flag.Arg(0))
	logFileName(0)
	go wait_sig()

	if C.tts_init() != 0 {
		panic("tts_init")
	}
	defer C.tts_close()

	reader := bufio.NewReader(fp)
	regex1 := regexp.MustCompile("\\s+")
	for {
		line1, _, err := reader.ReadLine()
		if err != nil {
			//log.Println(err)
			break
		}

		line1 = bytes.TrimSpace(line1)
		if len(line1) == 0 {
			continue
		}

		line1 = bytes.ReplaceAll(line1, []byte("<"), []byte("."))
		line1 = bytes.ReplaceAll(line1, []byte(">"), []byte("."))
		line1 = regex1.ReplaceAll(line1, []byte(" "))

		runes1 := bytes.Runes(line1)

		//fmt.Println(string(runes1))

		if len(runes1) < 20 {
			speakText(string(runes1), audioCh)
			continue
		}
		for {
			pos := getBreakPos(runes1)
			if len(runes1)-pos < 8 {
				pos = len(runes1)
			}
			if pos > 0 {
				speakText(string(runes1[0:pos]), audioCh)
			}
			if len(runes1) > pos {
				runes1 = runes1[pos:]
			} else {
				break
			}
		}
	}

	close(audioCh)
	wg.Wait()
	logFileName(-1)
}

func getFullName(fn string) string {
	if strings.HasPrefix(fn, "/") {
		return fn
	}

	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	return filepath.Join(wd, fn)
}

func logFileName(pos int) {
	home, err := os.UserHomeDir()
	if err != nil {
		panic(err)
	}
	s := fmt.Sprintf("File:%s\nPos:%d\n", fullName, pos)
	os.MkdirAll(filepath.Join(home, ".xfttsgo"), 777)
	os.Chdir(filepath.Join(home, ".xfttsgo"))
	ioutil.WriteFile("xfttsgo.log", []byte(s), 0644)

	ioutil.WriteFile(filepath.Join(filepath.Dir(fullName), "xfttsgo.log"), []byte(s), 0644)

}

func viewLog() {
	data, err := ioutil.ReadFile("xfttsgo.log")
	if err == nil {
		fmt.Println(string(data))
		return
	}

	home, err := os.UserHomeDir()
	if err != nil {
		panic(err)
	}
	data, err = ioutil.ReadFile(filepath.Join(home, ".xfttsgo", "xfttsgo.log"))
	if err != nil {
		fmt.Println("没有阅读记录。")
	} else {
		fmt.Println(string(data))
	}
}

func getBreakPos(data []rune) (pos int) {
	const ends1 = `。！？?!.`
	const ends2 = `;；:：,，`
	runes1 := bytes.Runes([]byte(ends1))
	runes2 := bytes.Runes([]byte(ends2))
	pos = 0
	//fmt.Println(len(data), max)
	for {
		for _, r := range runes1 {
			if r == data[pos] {
				//fmt.Println("return", pos)
				pos++
				return
			}
		}
		if pos > 60 {
			for _, r := range runes2 {
				if r == data[pos] {
					//fmt.Println("return", pos)
					pos++
					return
				}
			}
		}
		pos++
		if pos >= len(data) {
			break
		}
	}
	return
}

type PcmMsg struct {
	Text string
	Pcm  []byte
}

func speakText(s string, audioCh chan PcmMsg) {
	curPos++
	//fmt.Println("pos:", curPos)
	if curPos <= skipN {
		return
	}
	//fmt.Println(s)
	t := C.CString(s)
	defer C.free(unsafe.Pointer(t))

	if C.tts_puts(t) != 0 {
		panic("tts_puts")
	}

	var n C.int
	var errno C.int
	buf := bytes.NewBufferString("")
	for {
		p := C.tts_get_pcm(&n, &errno)
		if errno == -1 {
			break
		}
		if n > 0 && p != nil {
			data := C.GoBytes(p, n)
			buf.Write(data)
		}

	}

	audioCh <- PcmMsg{s, buf.Bytes()}
}

func playBack(ch chan PcmMsg, textCh chan string, wg *sync.WaitGroup) {
	defer wg.Done()

	var locker sync.Mutex
	go func() {
		var flag bool = false
		for {
			fmt.Scanln()
			if flag {
				locker.Unlock()
				flag = false
				textCh <- ">>继续阅读"
			} else {
				locker.Lock()
				flag = true
				textCh <- ">>阅读暂停"
			}
		}
	}()

	otoCtx, err := oto.NewContext(16000, 1, 2, 1024)
	if err != nil {
		panic(err)
	}
	player := otoCtx.NewPlayer()
	defer player.Close()

	for data := range ch {
		locker.Lock()
		locker.Unlock()
		textCh <- fmt.Sprintf(">%s\n", data.Text)
		player.Write(data.Pcm)
		playPosLog++
		//fmt.Println("play:", playPosLog)
	}

	fmt.Println("End playback")
}

var c chan os.Signal = make(chan os.Signal, 1)

func wait_sig() {
	signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGABRT, syscall.SIGQUIT)
	<-c
	fmt.Printf("中途退出\n阅读记录: \n%s\n%d\n", fullName, playPosLog)
	logFileName(playPosLog)
	os.Exit(0)
}
